﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace VersionComparer.Base
{

    /// <summary>
    /// This class builds an Rtf
    /// </summary>
    [Serializable]
    public class RtfBuilder
    {
        #region inner classes
        /// <summary>
        /// This class represents an Rtf part
        /// </summary>
        [Serializable]
        [DebuggerDisplay("Text={Text}")]
        public class RtfPart
        {
            #region constructors
            public RtfPart(string text, Color? foreColor, Color? backColor, FontStyle? fontStyle, int identation)
            {
                Identation = identation;
                Text = text;
                ForeColor = foreColor;
                BackColor = backColor;
                FontStyle = fontStyle;
            }
            #endregion constructors

            #region properties
            /// <summary>
            /// Rtf part identation
            /// </summary>
            public int Identation
            {
                get;
                set;
            }

            /// <summary>
            /// Rtf forecolor - if null, default color should be used
            /// </summary>
            public Color? ForeColor
            {
                get;
                set;
            }
            /// <summary>
            /// Rtf backcolor - if null, default color should be used
            /// </summary>
            public Color? BackColor
            {
                get;
                set;
            }
            /// <summary>
            /// Rtf text
            /// </summary>
            public string Text
            {
                get;
                set;
            }
            /// <summary>
            /// Rtf font style
            /// </summary>
            public FontStyle? FontStyle
            {
                get;
                set;
            }
            #endregion properties

            #region methods
            /// <summary>
            /// Create a clone for this part
            /// </summary>
            /// <returns></returns>
            public RtfPart Clone()
            {
                return new RtfPart(Text, ForeColor, BackColor, FontStyle, Identation);
            }
            /// <summary>
            /// Create a clone for this part with new text
            /// </summary>
            /// <param name="newText"></param>
            /// <returns></returns>
            public RtfPart Clone(string newText)
            {
                return new RtfPart(newText, ForeColor, BackColor, FontStyle, Identation);
            }
            /// <summary>
            /// Sets the values if not null
            /// </summary>
            /// <param name="foreColor"></param>
            /// <param name="backColor"></param>
            /// <param name="fontStyle"></param>
            public void SetIfNotNull(Color? foreColor, Color? backColor, FontStyle? fontStyle)
            {
                if ( foreColor != null )
                    ForeColor = foreColor;
                if ( backColor != null)
                    BackColor = backColor;
                if ( fontStyle != null)
                    FontStyle = fontStyle;
            }

            /// <summary>
            /// Returns true if other part is equal to the current one
            /// </summary>
            /// <param name="other">other rtf part</param>
            /// <returns></returns>
            public bool IsEqualTo(RtfPart other)
            {
                return Identation == other.Identation && ForeColor == other.ForeColor && BackColor == other.BackColor && FontStyle == other.FontStyle;
            }
            #endregion methods
        }

        /// <summary>
        /// This class represents an rtf part tag
        /// </summary>
        [Serializable]
        public class RtfPartTag: RtfPart
        {
            #region constructors
            public RtfPartTag(object tag):base ( null,null,null,null,0)
            {
                Tag = tag;
            }
            #endregion constructors

            #region properties
            public object Tag
            {
                get;
                private set;
            }
            #endregion properties
        }
        #endregion inner classes

        #region constants
        private const string defaultColorName = "\\cf0";
        private const string defaultHighlightColorName = "\\highlight0";
        #endregion constants

        #region fields

        long totalTextLength = 0;
        long maxTextSize = 100000;
        [NonSerialized]
        List<string> identationStrings;
        string allRtf;
        string identationString;
        string allText;
        private int identation = 0;
        List<RtfPart> parts = new List<RtfPart>();
        RtfPart lastPart;
        Dictionary<object, RtfPartTag> tags = new Dictionary<object, RtfPartTag>();
        #endregion fields

        #region constructors
        public RtfBuilder()
        {
            IdentationString = "   ";
        }
        #endregion constructors

        #region properties
        public bool IsOverflow
        {
            get
            {
                return totalTextLength > MaxTextSize || (allText.Length > MaxTextSize);
            }
            
        }
        public long MaxTextSize
        {
            get
            {
                return maxTextSize;
            }
            set
            {
                maxTextSize = value;
                allText = null;
                allRtf = null;
            }
        }
        public string Rtf
        {
            get
            {
                if (allRtf == null)
                    UpdateRtf();
                return allRtf;
            }
        }
        public string IdentationString
        {
            get
            {
                return identationString;
            }
            set
            {
                identationString = value;
                identationStrings = null;
            }
        }
        private bool protect;
        public bool Protect
        {
            get
            {
                return protect;
            }
            set
            {
                if (protect != value)
                {
                    protect = value;
                    allRtf = null;
                    allText = null;
                }
            }
        }
        public string Text
        {
            get
            {
                if (allText == null)
                {
                    UpdateRtf();   
                }
                return allText;
            }
        }
        public Color? BackColor
        {
            get;
            set;
        }
        public RtfPart[] Parts
        {
            get
            {
                return parts.ToArray();
            }
        }
        public object[] Tags
        {
            get
            {
                return tags.Keys.ToArray();
            }
        }

        #endregion properties


        private string[] Split(string fullText, string separator, bool ignoreCase)
        {
            List<string> result = new List<string> ();
            int pos = 0;
            int previousPos = pos;
            while (pos >= 0)
            {

                pos = fullText.IndexOf(separator, previousPos, ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture);
                if (pos >= 0)
                {
                    string textToAdd = fullText.Substring(previousPos, pos - previousPos);
                    result.Add(textToAdd);
                    previousPos = pos + separator.Length;
                }
                else
                {
                    if (previousPos == 0)
                        return null;
                    string textToAdd = fullText.Substring(previousPos);
                    result.Add(textToAdd);
                }
                
            }
            return result.ToArray();
        }
        private string GetIdentationLevel(int level)
        {
            if (level <= 0)
                return "";
            if (identationStrings == null)
                identationStrings = new List<string>();
            if (level < identationStrings.Count)
                return identationStrings[level];
            if (identationStrings.Count == 0)
                identationStrings.Add("");
            for (int i = identationStrings.Count; i <= level; i++)
                identationStrings.Add(identationStrings[i - 1] + IdentationString);
            return identationStrings[level];
        }
        private void UpdateRtf()
        {
            StringBuilder allTextBuilder = new StringBuilder();
            StringBuilder allRtfBuilder = new StringBuilder();
            Dictionary<Color, int> colors = new Dictionary<Color, int>();

            bool endedInEndOfLine = true;
            foreach (var part in parts)
            {
                string text = part.Text;
                if (text != null)
                {
                    string[] lines = text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
                    string identationLevel = GetIdentationLevel(part.Identation);
                    string foreColor = GetColorName(colors, part.ForeColor, false);
                    string backColor = GetColorName(colors, part.BackColor, true);
                    string fontStyleStart = GetFontStyleStart(part.FontStyle);
                    string fontStyleEnd = GetFontStyleEnd(part.FontStyle);
                    for (int i = 0; i < lines.Length; i++)
                    {

                        if (i > 0)
                        {
                            allTextBuilder.AppendLine();
                            allRtfBuilder.AppendLine("\\par");
                            endedInEndOfLine = true;
                        }
                        string line = lines[i];
                        if (string.IsNullOrEmpty(line))
                            endedInEndOfLine = true;
                        else
                        {
                            string start = "";
                            string end = "";
                            if (endedInEndOfLine)
                            {
                                allTextBuilder.Append(identationLevel);
                                allRtfBuilder.Append(identationLevel);
                            }
                            endedInEndOfLine = false;
                            allTextBuilder.Append(line);
                            line = line.Replace("{", "\\{").Replace("}", "\\}");
                            if (foreColor != null)
                            {
                                start += foreColor;
                                end += defaultColorName;
                            }

                            if (backColor != null)
                            {
                                start += backColor;
                                end += defaultHighlightColorName;
                            }
                            if (fontStyleStart != null)
                            {
                                start += fontStyleStart;
                                end += fontStyleEnd;
                            }
                            if (start.Length > 0)
                                line = string.Format("{0} {1}{2} ", start, line, end);
                            allRtfBuilder.Append(line);
                        }
                    }
                }
                if (allTextBuilder.Length > MaxTextSize)
                {
                    break;
                }
            }
            allRtf = string.Format("{0}{1}", BuildHeader(colors), allRtfBuilder.ToString());
            allText = allTextBuilder.ToString();
        }
        public void SetTextProperties(string text, bool ignoreCase, Color? foreColor, Color? backColor, FontStyle? fontStyle)
        {
            if (string.IsNullOrEmpty(text))
                return;
            bool changed = false;
            for (int i = 0; i < parts.Count; i++)
            {
                var part = parts[i];
                if (part.Text != null)
                {
                    string[] textParts = Split(part.Text, text, ignoreCase);
                    if (textParts != null )
                    {
                        if (textParts.Length == 0)
                        {
                            part.SetIfNotNull(foreColor, backColor, fontStyle);
                        }
                        else
                        {
                            List<RtfPart> newParts = new List<RtfPart> ();
                            for (int textPartIndex = 0; textPartIndex < textParts.Length; textPartIndex++)
                            {
                                if (textPartIndex > 0)
                                {

                                    var newPart = part.Clone(text);
                                    newPart.SetIfNotNull(foreColor, backColor, fontStyle);
                                    newParts.Add(newPart);
                                }
                                if( !string.IsNullOrEmpty ( textParts[textPartIndex ]))
                                    newParts.Add(part.Clone(textParts[textPartIndex]));
                            }
                            parts.RemoveAt(i);
                            parts.InsertRange(i, newParts);
                            i += newParts.Count - 1;
                        }
                    }
                }
            }
            if (changed)
            {
                allRtf = null;
                allText = null;
            }
        }
        private Color? DimColor(Color? color, float dimFactor)
        {
            if (color == null)
                color = Color.Black;

            return Color.FromArgb ((int)( 255 * dimFactor), color.Value);
        }
        public void Flush()
        {
            if ( allRtf == null )
                UpdateRtf();
        }
        public void DimColors(float dimFactor)
        {
            if (dimFactor > 1)
                dimFactor = 1;
            if (dimFactor < 0)
                dimFactor = 0;
            foreach (var part in Parts)
                part.ForeColor = DimColor(part.ForeColor, dimFactor);
        }
        private string BuildHeader(Dictionary<Color, int> colors)
        {
            StringBuilder colorsList = new StringBuilder();
            foreach (Color aux in colors.Keys)
            {
                Color color = aux;

                float colorFactor = ((float)color.A)/255 ;
                if (colorFactor != 1.0)
                {
                    color = ControlPaint.Light(color, 1 + colorFactor);
                    //colorDiff = 200;
                    //colorFactor = 1.0f;
                    //colorFactor = 0.2f;
                }
                colorsList.AppendFormat("\\red{0}\\green{1}\\blue{2};", color.R, color.G, color.B);
            }
            return string.Format(
@"{{\rtf1\ansi\ansicpg1252\deff0\deflang2070{{\fonttbl{{\f0\fnil Consolas;}}}}
{{\colortbl ;{0}}}
\viewkind4\uc1\pard{1}\f0\fs17", colorsList.ToString(), Protect ? "\\protect" : "");
        }
        private static string GetFontStyleStart(FontStyle? fontStyle)
        {
            StringBuilder header = new StringBuilder ();
            if (fontStyle == null)
                return null;
            if ((fontStyle.Value & FontStyle.Bold )== FontStyle.Bold)
                header.Append("\\b ");

            if ((fontStyle.Value & FontStyle.Italic) == FontStyle.Italic)
                header.Append("\\i ");

            if ((fontStyle.Value & FontStyle.Underline) == FontStyle.Underline)
                header.Append("\\ul ");

            if ((fontStyle.Value & FontStyle.Strikeout) == FontStyle.Strikeout)
                header.Append("\\strike ");

            return header.ToString();
        }
        private static string GetFontStyleEnd(FontStyle? fontStyle)
        {
            StringBuilder header = new StringBuilder();
            if (fontStyle == null)
                return null;
            if ((fontStyle.Value & FontStyle.Bold) == FontStyle.Bold)
                header.Append("\\b0 ");

            if ((fontStyle.Value & FontStyle.Italic) == FontStyle.Italic)
                header.Append("\\i0 ");

            if ((fontStyle.Value & FontStyle.Underline) == FontStyle.Underline)
                header.Append("\\ul0 ");

            if ((fontStyle.Value & FontStyle.Strikeout) == FontStyle.Strikeout)
                header.Append("\\strike0 ");

            return header.ToString();
        }
        private static int GetColorId( Dictionary<Color, int> colors , Color color)
        {
            int colorID;
            if (!colors.TryGetValue(color, out colorID))
                colors.Add(color, colorID = colors.Count + 1);
            return colorID;
        }
        private static string GetColorName(Dictionary<Color, int> colors, Color? color, bool isHighlight)
        {
            if (color == null)
                return null;
            if (color.Value == Color.Empty)
                return null;
            if (color.Value != null && color.Value.A < 255)
            {
                if (isHighlight)
                {
                }
            }
            int colorID = GetColorId(colors, color.Value);
            return isHighlight ? string.Format("\\highlight{0}", colorID) : string.Format("\\cf{0}", colorID);
        }
        private int SetIdentation(int newIdentation)
        {
            identation = newIdentation;
            if (identation < 0)
                identation = 0;
            return identation;
        }
        public int Ident(int identations = 1)
        {
            return SetIdentation(identation + identations);
        }
        public int DeIdent(int identations = 1)
        {
            return SetIdentation(identation - identations);
        }
        private string ApplyStyles(string rtfToAdd, string forecolor, string backColor, string fontStyleStart, string fontStyleEnd)
        {
            if ( forecolor != null )
                rtfToAdd = string.Format("{0}{1}{2}",forecolor , rtfToAdd, defaultColorName);

            if (backColor != null)
                rtfToAdd = string.Format("{0}{1}{2}", backColor, rtfToAdd, defaultHighlightColorName);

            if (fontStyleStart != null)
                rtfToAdd = string.Format("{0}{1}{2}", fontStyleStart, rtfToAdd, fontStyleEnd);

            return rtfToAdd;
            
        }
        public void Append(RtfBuilder other)
        {
            for (int i = 0; i < other.parts.Count; i++)
            {
                var newPart = other.parts[i].Clone();
                newPart.Identation += identation;
                if (newPart.BackColor == null && BackColor != null)
                    newPart.BackColor = BackColor;

                parts.Add(newPart);
            }

            totalTextLength += other.totalTextLength;
            if (totalTextLength > MaxTextSize)
                throw new OverflowException();
            lastPart = parts.Last();
            allRtf = null;
            allText = null;
        }
        public RtfPartTag GetTaggedPart(object tag)
        {
            RtfPartTag result;
            if (tag == null)
                return null;
            if (tags.TryGetValue(tag, out result))
                return result;
            return null;
        }
        public void SetTag ( object tag )
        {
            if ( tag  == null )
                return;
            var newPart = new RtfPartTag(tag);
            parts.Add(lastPart =newPart );
            tags.Add(tag, newPart);
        }
        public void Append(string text, Color? foreColor = null, Color? backColor = null, FontStyle? fontStyle = null)
        {
            allRtf = null;
            allText = null;
            if (backColor == null)
                backColor = BackColor;
            totalTextLength += text.Length;
            if (totalTextLength > MaxTextSize)
                throw new OverflowException();
            RtfPart newPart = new RtfPart(text, foreColor, backColor, fontStyle, identation);

            if (lastPart != null)
            {
                if (lastPart.IsEqualTo(newPart))
                    lastPart.Text += newPart.Text;
                else
                    parts.Add(lastPart = newPart);
            }
            else
                parts.Add(lastPart = newPart);
        }
        public void AppendLine(string text, Color? foreColor = null, Color? backColor = null, FontStyle? fontStyle = null)
        {
            Append(text + "\r\n", foreColor, backColor, fontStyle);
        }
        public void AppendLine()
        {
            AppendLine("");
        }
    }
}
